#include "console.h"
#include "klib.h"
#include "cpu_instr.h"
#include "irq.h"
#include "tty.h"
#include "kbd.h"
#include "types.h"

#define CONSOLE_NR 8
static console_t console_buf[CONSOLE_NR];
static uint8_t   curr_console_idx = 0;

static int read_cursor_pos(void)
{
    irq_state_t irq_state = irq_enter_protect();
#if 0
    outb(0x3D4,0x0a);
    int start_line=inb(0x3d5);
    if(start_line==13){
        outb(0x3d4,0x0e);
    }
    outb(0x3D4,0x0b);
    int end_line=inb(0x3d5);
    if(end_line==14){
        outb(0x3d4,0x0f);
    }
#endif

    int pos = 0;
    outb(0x3D4, 0x0F);    // write low addr
    pos = inb(0x3d5);
    outb(0x3d4, 0xe);    // write high addr
    pos |= inb(0x3d5) << 8;
    irq_exit_protect(irq_state);
    return pos;
}

static int update_cursor_pos(console_t *console)
{
    irq_state_t irq_state = irq_enter_protect();
    uint16_t    pos       = (console - console_buf) * (console->display_cols * console->display_rows);
    pos += console->cursor_row * console->display_cols + console->cursor_col;
    outb(0x3d4, 0x0f);    // write low
    outb(0x3d5, (uint8_t)(pos & 0xff));
    outb(0x3d4, 0x0e);    // write high
    outb(0x3d5, (uint8_t)((pos >> 8) & 0xff));
    irq_exit_protect(irq_state);
    return pos;
}

static void clear_display(console_t *console)
{
    for (int i = 0; i < console->display_cols * console->display_rows; i++)
    {
        (console->disp_base + i)->c          = 0;
        (console->disp_base + i)->foreground = console->foreground;
        (console->disp_base + i)->background = console->background;
    }
}
int console_init(int console_idx)
{
    if (console_idx < 0 || (console_idx >= CONSOLE_NR))
    {
        return -1;
    }
    console_t *console    = console_buf + console_idx;
    console->display_cols = CONSOLE_COL_MAX;
    console->display_rows = CONSOLE_ROW_MAX;
    console->disp_base = (disp_char_t *)CONSOLE_DISP_ADDR + console_idx * console->display_cols * console->display_rows;
    console->foreground = COLOR_White;
    console->background = COLOR_Black;

    if (console_idx == 0)
    {
        int cursor_pos      = read_cursor_pos();
        console->cursor_row = cursor_pos / console->display_cols;
        console->cursor_col = cursor_pos % console->display_cols;
    }
    else
    {
        console->cursor_row = console->cursor_col = 0;
        clear_display(console);
    }
    console->old_cursor_row = console->cursor_row;
    console->old_cursor_col = console->cursor_col;
    console->write_state    = CONSOLE_WRITE_NORMAL;
    mutex_init(&console->mutex);
    return 0;
}
static void scroll_up(console_t *console, int lines)
{
    disp_char_t *dest = console->disp_base;
    disp_char_t *src  = console->disp_base + console->display_cols * lines;
    uint32_t     size = (console->display_rows - lines) * console->display_cols * sizeof(disp_char_t);
    kernel_memcpy((void *)dest, (void *)src, size);
    kernel_memset((void *)(console->disp_base + size / sizeof(disp_char_t)),
                  0,
                  lines * console->display_cols * sizeof(disp_char_t));
    // move curor to the right place
    console->cursor_row -= lines;
}

static void show_char(console_t *console, char ch)
{
    int          offset = console->cursor_col + console->cursor_row * console->display_cols;
    disp_char_t *p      = console->disp_base + offset;
    p->c                = ch;
    p->foreground       = console->foreground;
    p->background       = console->background;
}
void set_font_style(console_t *console)
{
    static const color_t color_table[] = {
        COLOR_Black,
        COLOR_Red,
        COLOR_Green,
        COLOR_Yellow,
        COLOR_Blue,
        COLOR_Magenta,
        COLOR_Cyan,
        COLOR_White,
    };
    for (int i = 0; i <= console->curr_param_index; i++)
    {
        int param = console->esc_param[i];
        if (param >= 30 && param <= 37)
        {
            console->foreground = color_table[param - 30];
        }
        else if (param >= 40 && param <= 47)
        {
            console->background = color_table[param - 40];
        }
        if (param == 39)
        {
            console->foreground = COLOR_White;
        }
        if (param == 49)
        {
            console->background = COLOR_Black;
        }
    }
}
void deal_cursor_overflow(console_t *console)
{
    // cursor downward overflow
    if (console->cursor_col < 0)
    {
        console->cursor_col = console->display_cols - 1;
        if (--console->cursor_row < 0)
        {
            console->cursor_row = 0;
        }
    }
    // cursor upward overflow
    if (console->cursor_col >= console->display_cols)
    {
        console->cursor_col = console->cursor_col - console->display_cols;
        console->cursor_row++;
    }
    if (console->cursor_row >= console->display_rows)
    {
        scroll_up(console, console->cursor_row - console->display_rows + 1);
    }
}
char console_char(console_t *console)
{
    disp_char_t *disp_char = console->disp_base + console->cursor_row * console->display_cols + console->cursor_col;
    return disp_char->c;
}
int console_write(int console_idx, char *ptr, int size)
{
    int        pre_size    = size;
    char      *origin_char = ptr;
    console_t *console     = console_buf + console_idx;
    mutex_lock(&console->mutex);
    while (size--)
    {
        char ch = *ptr++;
        switch ((uint8_t)console->write_state)
        {
        case CONSOLE_WRITE_E0:
            switch ((uint8_t)ch)
            {
            case KEY_LEFT:
                if (console->cursor_col != 0x0)
                {
                    console->cursor_col--;
                }
                else
                {
                    console->cursor_col = console->display_cols - 1;
                    console->cursor_row = console->cursor_row--;
                    deal_cursor_overflow(console);
                }
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case KEY_RIGHT:
                if (console->cursor_col != console->display_cols - 1)
                {
                    console->cursor_col++;
                }
                else
                {
                    console->cursor_col = 0;
                    console->cursor_row = console->cursor_row++;
                    deal_cursor_overflow(console);
                }
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case KEY_UP:
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case KEY_DOWN:
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            default:
                break;
            }
            break;
        case CONSOLE_WRITE_SQUARE:
            switch (ch)
            {
            case ';':
                console->curr_param_index++;
                break;
            case 'm':
                set_font_style(console);
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case 'D':
                console->cursor_col -= console->esc_param[0];
                deal_cursor_overflow(console);
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case 'C':
                // console->cursor_col +=console->esc_param[0];
                // deal_cursor_overflow(console);
                for (int i = 0; i < console->esc_param[0]; i++)
                {
                    console->write_state = CONSOLE_WRITE_NORMAL;
                    console_write(console_idx, " ", 1);
                    console->write_state = CONSOLE_WRITE_SQUARE;
                }
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case 'H':
                console->cursor_row  = console->esc_param[0];
                console->cursor_col  = console->esc_param[1];
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case 'J':
                int param0 = console->esc_param[0];
                if (param0 == 2)
                {
                    clear_display(console);
                    console->cursor_col = 0;
                    console->cursor_row = 0;
                }
                else if (param0 == 1)
                {
                    for (int i = 0; i < console->display_cols; i++)
                    {
                        (console->disp_base + console->cursor_row * console->display_cols + i)->v = 0;
                    }
                    console->cursor_col = 0;
                }
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            default:
                if (ch >= '0' && ch <= '9')
                {
                    int num                                       = ch & 0x0f;
                    int tmp                                       = console->esc_param[console->curr_param_index];
                    console->esc_param[console->curr_param_index] = tmp * 10 + num;
                }
                break;
            }
            break;
        case CONSOLE_WRITE_ESC:
            switch (ch)
            {
            case '7':
                console->old_cursor_row = console->cursor_row;
                console->old_cursor_col = console->cursor_col;
                console->write_state    = CONSOLE_WRITE_NORMAL;
                break;
            case '8':
                console->cursor_row  = console->old_cursor_row;
                console->cursor_col  = console->old_cursor_col;
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            case '[':
                console->write_state      = CONSOLE_WRITE_SQUARE;
                console->curr_param_index = 0;
                for (int i = 0; i < ESC_PARAM_MAX; i++)
                {
                    console->esc_param[i] = 0;
                }
                break;
            default:
                console->write_state = CONSOLE_WRITE_NORMAL;
                break;
            }
            break;
        case CONSOLE_WRITE_NORMAL:
            switch ((uint8_t)ch)
            {
            case ASCII_ESC:
                console->write_state = CONSOLE_WRITE_ESC;
                break;
            case 0xE0:
                console->write_state = CONSOLE_WRITE_E0;
                break;
            case '\r':
                console->cursor_col = 0;
                continue;
            case '\n':
                // console->cursor_col=0;
                console->cursor_row++;
                if (console->cursor_row >= console->display_rows)
                {
                    scroll_up(console, 1);
                }
                break;
            case '\t':
                int   count_before = 0;
                char *tmp          = ptr - 2;
                while (tmp >= origin_char && *tmp > ' ' && *tmp <= '~')
                {
                    count_before++;
                    tmp--;
                }
                int space_count = 8 - (count_before - (count_before / 8) * 8);
                for (int i = 0; i < space_count; i++)
                {
                    console_write(console_idx, " ", 1);
                }
                break;
            case 0x7f:
                console->cursor_col--;
                deal_cursor_overflow(console);
                show_char(console, '\0');
                break;
            case '\b':
                console->cursor_col--;
                deal_cursor_overflow(console);
                break;
            default:
                if (ch >= ' ' && ch <= '~')
                {
                    show_char(console, ch);
                    console->cursor_col++;
                    deal_cursor_overflow(console);
                }
                break;
            }
            break;
        }
    }
    mutex_unlock(&console->mutex);
    if (console_idx == curr_console_idx)
    {
        update_cursor_pos(console);
    }
    return pre_size - size;
}
void console_close(int console)
{
}
int console_write_tty(tty_t *tty)
{
    console_t *console = console_buf + tty->console_idx;
    int        len     = 0;
    do
    {
        char c;
        int  err = tty_fifo_get(&tty->ofifo, &c);
        if (err < 0)
        {
            break;
        }
        len++;
        sem_notify(&tty->osem);
        if ((tty->oflags & TTY_OCRLF) && (c == '\n'))
        {
            console_write(tty->console_idx, "\r", 1);
        }
        console_write(tty->console_idx, &c, 1);
    } while (1);
    if (tty->console_idx == curr_console_idx)
    {
        update_cursor_pos(console);
    }
    return len;
}
void console_select(int console_idx)
{
    int        show_tty_flag = 0;
    console_t *console       = console_buf + console_idx;
    if (console->disp_base == 0)
    {
        show_tty_flag = 1;
        console_init(console_idx);
    }
    uint16_t pos = console_idx * console->display_cols * console->display_rows;
    outb(0x3d4, 0xc);
    outb(0x3d5, (uint8_t)((pos >> 8) & 0xff));
    outb(0x3d4, 0xd);
    outb(0x3d5, (uint8_t)(pos & 0xff));
    curr_console_idx = console_idx;
    update_cursor_pos(console);
    if (show_tty_flag)
    {
        char tty_msg[7] = "tty0:";
        tty_msg[3]      = console_idx + '0';
        tty_msg[4]      = ':';
        tty_msg[5]      = '\n';
        tty_msg[6]      = '\0';
        console_write(console_idx, tty_msg, kernel_strlen(tty_msg));
    }
}